/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.XPath;

namespace DaveSexton.DocProject.Sandcastle.TopicManagement
{
	public sealed class ConceptualTopicFileManager : MarshalByRefObject
	{
		#region Public Properties
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1802:UseLiteralsWhereAppropriate")]
		public static readonly string ApiProjectTopicText = "Namespaces";
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1802:UseLiteralsWhereAppropriate")]
		public static readonly string TopicFileExtension = ".aml";
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1802:UseLiteralsWhereAppropriate")]
		public static readonly string TopicFileGeneralExtension = ".xml";
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1802:UseLiteralsWhereAppropriate")]
		public static readonly string TopicFileCompanionExtension = ".cmp";

		public TopicsDocument Topics
		{
			get
			{
				return topics;
			}
		}

		public string TopicsPath
		{
			get
			{
				return topicsPath;
			}
		}

		public Uri ConceptualTemplatesDirectory
		{
			get
			{
				EnsureConceptualTemplates();

				return conceptualTemplatesDirectory;
			}
		}

		public ICollection<ConceptualTemplate> ConceptualTemplates
		{
			get
			{
				EnsureConceptualTemplates();

				return conceptualTemplates.AsReadOnly();
			}
		}
		#endregion

		#region Private / Protected
		private readonly TopicsDocument topics;
		private readonly IDocProject project;
		private readonly string topicsPath;
		private Uri conceptualTemplatesDirectory;
		private List<ConceptualTemplate> conceptualTemplates;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="ConceptualTopicFileManager" /> class.
		/// </summary>
		public ConceptualTopicFileManager(IDocProject project, string topicsPath, string topicsFile)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			if (string.IsNullOrEmpty(topicsPath))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "topicsPath");

			if (string.IsNullOrEmpty(topicsFile))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "topicsFile");

			this.project = project;
			this.topicsPath = topicsPath;

			topics = new TopicsDocument(topicsFile);
		}
		#endregion

		#region Methods
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static int CreateMergedToc(string topicsFile, string mergeWithTocFile, string outputTocFile)
		{
			if (string.IsNullOrEmpty(topicsFile))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "topicsFile");

			if (string.IsNullOrEmpty(mergeWithTocFile))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "mergeWithTocFile");

			if (string.IsNullOrEmpty(outputTocFile))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "outputTocFile");

			return WriteTocTopicsRecursive(topicsFile, mergeWithTocFile, outputTocFile);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static int CreateToc(string topicsFile, string outputTocFile)
		{
			if (string.IsNullOrEmpty(topicsFile))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "topicsFile");

			if (string.IsNullOrEmpty(outputTocFile))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "outputTocFile");

			return WriteTocTopicsRecursive(topicsFile, null, outputTocFile);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		private static int WriteTocTopicsRecursive(string topicsFile, string mergeWithTocFile, string outputTocFile)
		{
			int conceptualTopicCount = 0;

			XmlReaderSettings readerSettings = new XmlReaderSettings();
			readerSettings.IgnoreComments = true;
			readerSettings.IgnoreProcessingInstructions = true;
			readerSettings.IgnoreWhitespace = true;

			using (XmlReader reader = XmlReader.Create(topicsFile, readerSettings))
			{
				reader.ReadStartElement("topics");

				XmlWriterSettings writerSettings = new XmlWriterSettings();
				writerSettings.Encoding = Encoding.UTF8;
				writerSettings.Indent = true;

				using (XmlWriter writer = XmlWriter.Create(outputTocFile, writerSettings))
				{
					writer.WriteStartDocument();
					writer.WriteStartElement("topics");

					WriteTocTopicsRecursive(writer, reader, mergeWithTocFile, ref conceptualTopicCount);

					writer.WriteEndElement();
					writer.WriteEndDocument();
				}
			}

			return conceptualTopicCount;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		private static void WriteTocTopicsRecursive(XmlWriter writer, XmlReader reader, string mergeWithTocFile, ref int conceptualTopicCount)
		{
			while (!reader.EOF)
			{
				if (reader.IsStartElement("topic", ""))
				{
					bool exposed;
					if (!bool.TryParse(reader.GetAttribute("expose", ""), out exposed) || exposed)
					{
						conceptualTopicCount++;

						writer.WriteStartElement("topic");

						string file = reader.GetAttribute("file", "");
						string id = reader.GetAttribute("id", "");

						if (file.EndsWith(".htm", StringComparison.OrdinalIgnoreCase) || file.EndsWith(".html", StringComparison.OrdinalIgnoreCase))
							writer.WriteAttributeString("file", System.IO.Path.GetFileNameWithoutExtension(file));
						else
							// NOTE: Ignore the file attribute and use the id instead since the file may have a friendly name that was 
							// created by the author for easy file management.
							writer.WriteAttributeString("file", id);

						writer.WriteAttributeString("id", id);

						using (XmlReader topicReader = reader.ReadSubtree())
						{
							topicReader.ReadStartElement();

							WriteTocTopicsRecursive(writer, topicReader, mergeWithTocFile, ref conceptualTopicCount);
						}

						writer.WriteEndElement();
					}
				}
				else if (reader.IsStartElement(TopicsDocument.ApiRootPlaceholderElementName)
					&& !string.IsNullOrEmpty(mergeWithTocFile))
				{
					using (XmlReader tocReader = XmlReader.Create(mergeWithTocFile, reader.Settings))
					{
						tocReader.ReadStartElement();

						while (tocReader.NodeType == XmlNodeType.Element)
							writer.WriteNode(tocReader, false);
					}
				}

				reader.Skip();
			}
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static int CreateConceptualBuildFiles(string topicsFile, string manifestFile, string topicsFolder, string topicsWorkingFolder, string metadataWorkingFolder)
		{
			if (string.IsNullOrEmpty(topicsFile))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "topicsFile");

			if (string.IsNullOrEmpty(manifestFile))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "manifestFile");

			if (string.IsNullOrEmpty(topicsFolder))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "topicsFolder");

			if (string.IsNullOrEmpty(topicsWorkingFolder))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "topicsWorkingFolder");

			if (string.IsNullOrEmpty(metadataWorkingFolder))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "metadataWorkingFolder");

			int companionFilesCount = 0;

			ClearDirectory(topicsWorkingFolder);
			ClearDirectory(metadataWorkingFolder);

			XmlReaderSettings readerSettings = new XmlReaderSettings();
			readerSettings.IgnoreComments = true;
			readerSettings.IgnoreProcessingInstructions = true;
			readerSettings.IgnoreWhitespace = true;

			using (XmlReader reader = XmlReader.Create(topicsFile, readerSettings))
			{
				XmlWriterSettings writerSettings = new XmlWriterSettings();
				writerSettings.Encoding = Encoding.UTF8;
				writerSettings.Indent = true;

				using (XmlWriter writer = XmlWriter.Create(manifestFile, writerSettings))
				{
					writer.WriteStartDocument();
					writer.WriteStartElement("topics");

					bool exposed;

					while (reader.Read())
					{
						if (reader.IsStartElement("topic", "")
							&& (!bool.TryParse(reader.GetAttribute("expose", ""), out exposed) || exposed))
						{
							string file = reader.GetAttribute("file", "");

							if (!file.EndsWith(".htm", StringComparison.OrdinalIgnoreCase) && !file.EndsWith(".html", StringComparison.OrdinalIgnoreCase))
							{
								string id = reader.GetAttribute("id", "");

								WriteManifestTopic(writer, id, file);

								SystemPath topicFile = new SystemPath(topicsFolder, file, false, false);

								topicFile.CopyTo(new SystemPath(topicsWorkingFolder, id + TopicFileGeneralExtension, false, false), true);

								string sourceMetadataFile = topicFile.ChangeExtension(TopicFileCompanionExtension);
								string targetMetadataFile = Path.Combine(metadataWorkingFolder, id + TopicFileCompanionExtension + ".xml");

								CreateXmlCompanionFile(sourceMetadataFile, targetMetadataFile, id, System.IO.Path.GetFileNameWithoutExtension(file));

								companionFilesCount++;
							}
						}
					}

					writer.WriteEndElement();
					writer.WriteEndDocument();
				}
			}

			return companionFilesCount;
		}

		[System.Diagnostics.DebuggerHidden]
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		private static void CreateXmlCompanionFile(string sourceMetdataFile, string targetMetadataFile, string topicId, string defaultTocTitle)
		{
			TopicMetadata metadata = null;

			try
			{
				TopicMetadataCollection sourceMetadata = new TopicMetadataXml(sourceMetdataFile).ReadAll();

				if (sourceMetadata.Count > 0)
					metadata = sourceMetadata[0];
			}
			catch (FileNotFoundException) { }
			catch (DirectoryNotFoundException) { }

			TopicMetadataXml targetMetadata = new TopicMetadataXml(targetMetadataFile);

			targetMetadata.WriteCompanionFile(topicId, defaultTocTitle, metadata);
		}

		[System.Diagnostics.DebuggerHidden]
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		private static void ClearDirectory(string path)
		{
			if (Directory.Exists(path))
			{
				try
				{
					Directory.Delete(path, true);
				}
				catch (DirectoryNotFoundException) { }
			}

			Directory.CreateDirectory(path);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		private static void WriteManifestTopic(XmlWriter writer, string id, string file)
		{
			if (!string.IsNullOrEmpty(file))
			{
				writer.WriteStartElement("topic");
				writer.WriteAttributeString("id", id);
				writer.WriteEndElement();
			}
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		private static void WriteDefaultTemplate(string id, string path, FileMode mode)
		{
			using (StreamWriter writer = new StreamWriter(
				File.Open(path, mode, FileAccess.Write, FileShare.None), Encoding.UTF8))
			{
				WriteDefaultTemplate(id, writer);
			}
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		private static void WriteDefaultTemplate(string id, TextWriter writer)
		{
			writer.Write(Resources.Documents.DefaultConceptualTemplate.Replace("$(ID)", id));
		}

		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand,
			Flags = System.Security.Permissions.SecurityPermissionFlag.Infrastructure)]
		public override object InitializeLifetimeService()
		{
			return null;
		}

		private void EnsureConceptualTemplates()
		{
			if (conceptualTemplates == null)
			{
				string presentationName = ((SandcastleProjectOptions) project.Options).PresentationName;

				Configuration.Presentation presentation = SandcastleBuildEngineProvider.Presentations[presentationName];

				conceptualTemplatesDirectory = new Uri(PathFormat.EnsureDirectory(presentation.ConceptualTemplatesDirectory));

				string[] files = Directory.GetFiles(conceptualTemplatesDirectory.LocalPath, "*" + TopicFileGeneralExtension, SearchOption.AllDirectories);

				conceptualTemplates = new List<ConceptualTemplate>(files.Length);

				foreach (string file in files)
				{
					Uri uri = new Uri(file);
					uri = conceptualTemplatesDirectory.MakeRelativeUri(uri);

					string relativePath = System.Web.HttpUtility.UrlDecode(uri.ToString());

					conceptualTemplates.Add(new ConceptualTemplate(conceptualTemplatesDirectory.LocalPath, relativePath));
				}
			}
		}

		public void ForEachElementRecursive(IXPathNavigable startElement, Action<IXPathNavigable> action)
		{
			if (startElement == null)
				throw new ArgumentNullException("startElement");

			if (action == null)
				throw new ArgumentNullException("action");

			ForEachElementRecursiveInternal(startElement.CreateNavigator(), action);
		}

		private void ForEachElementRecursiveInternal(XPathNavigator element, Action<IXPathNavigable> action)
		{
			action(element);

			foreach (XPathNavigator child in element.SelectChildren(XPathNodeType.Element))
			{
				if (!TopicsDocument.IsApiRoot(child))
					ForEachElementRecursiveInternal(child, action);
			}
		}

		public Topic CreateNewTopic(IXPathNavigable element, Guid id, string title, ConceptualTemplate template,
			string copyFromCompanionFile, Predicate<Guid> validateId)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			if (id == Guid.Empty)
			{
				if (template == null)
					id = Guid.NewGuid();
				else
				// Try to use the same Guid so that conceptual links aren't broken if template 
				// represents a topic that is being imported.
				{
					id = GetGuid(template.FullPath);

					if (id == Guid.Empty && !TryParseGuid(template.Name, out id))
						id = Guid.NewGuid();
				}
			}

			while (!validateId(id))
				id = Guid.NewGuid();

			string idString = id.ToString();
			string baseFileName = PathFormat.EnsureFileName(title ?? idString);
			string fileName = baseFileName + TopicFileExtension;
			int i = 0;

			do
			// Loop in case a file already exists with the current name, like when a topic is imported twice
			{
				try
				{
					CreateTopicFile(idString, fileName, FileMode.CreateNew, template, true, copyFromCompanionFile);
					break;
				}
				catch (IOException) { }

				if (title == null)
					fileName = (idString = Guid.NewGuid().ToString()) + TopicFileExtension;
				else
					fileName = (baseFileName + ++i) + TopicFileExtension;
			}
			while (true);

			// NOTE: Do not pass in a title for conceptual topics since the TOC title is managed externally by another class
			return new Topic(TopicType.Conceptual, element, idString, null, fileName);
		}

		[System.Diagnostics.DebuggerHidden]
		private static bool TryParseGuid(string name, out Guid guid)
		{
			try
			{
				guid = new Guid(name);
				return true;
			}
			catch (FormatException) { }
			catch (OverflowException) { }

			guid = Guid.Empty;
			return false;
		}

		[System.Diagnostics.DebuggerHidden]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Guid")]
		private static bool IsGuid(string value)
		{
			try
			{
				new Guid(value);
				return true;
			}
			catch (FormatException) { }
			catch (OverflowException) { }

			return false;
		}

		[System.Diagnostics.DebuggerHidden]
		private static Guid GetGuid(string topicFile)
		{
			try
			{
				XmlReaderSettings settings = new XmlReaderSettings();
				settings.IgnoreComments = true;
				settings.IgnoreWhitespace = true;
				settings.IgnoreProcessingInstructions = true;

				using (XmlReader reader = XmlReader.Create(topicFile, settings))
				{
					reader.MoveToContent();

					if (reader.LocalName == "topic")
					{
						Guid guid;
						if (TryParseGuid(reader.GetAttribute("id", ""), out guid))
							return guid;
					}
				}
			}
			catch (ArgumentException) { }
			catch (XmlException) { }
			catch (UriFormatException) { }
			catch (FileNotFoundException) { }
			catch (DirectoryNotFoundException) { }

			return Guid.Empty;
		}

		/// <param name="topicFileNames">Dictionary of <see cref="Guid"/> (as strings) and file name pairs that may contain a friendly 
		/// file name for the specified <paramref name="target"/> file; used only when the <paramref name="target"/>'s file name is a 
		/// <see cref="Guid"/> and a title was not found in a companion file.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DaveSexton.DocProject.Sandcastle.TopicManagement.ConceptualTopicFileManager.TryParseGuid(System.String,System.Guid@)")]
		public static void CopyTopicFile(IDocProject project, string source, string target, IDictionary<string, string> topicFileNames)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			Guid fileNameGuid;
			Guid id = GetGuid(source);		// NOTE: The GUID inside the topic must have precedence over the file name, if it's also a GUID.

			if (id == Guid.Empty)
			{
				if (!TryParseGuid(System.IO.Path.GetFileNameWithoutExtension(source), out fileNameGuid))
					throw new ArgumentException(Resources.Errors.InvalidTopicsFileMissingGuid, "source");
			}
			else
				// Get the file name if it's a GUID; ignore the result since the default is Guid.Empty anyway.
				TryParseGuid(System.IO.Path.GetFileNameWithoutExtension(source), out fileNameGuid);

			string sourceMetadataFile = System.IO.Path.ChangeExtension(source, TopicFileCompanionExtension);

			// NOTE: If the file doesn't exist null is returned but that's OK since null may be passed to the 
			// WriteCompanionFile method that is called below.
			TopicMetadata sourceMetadata = TopicMetadataXml.Load(sourceMetadataFile, id.ToString());

			string idString = id.ToString("D");
			string changedFileNameTo = null;

			if (fileNameGuid != Guid.Empty)
			{
				if (sourceMetadata != null)
				{
					string title = sourceMetadata.Title;
					bool titleChanged = false;

					if (string.IsNullOrEmpty(title) || IsGuid(title))
					{
						title = sourceMetadata.TocTitle;
						titleChanged = true;
					}

					if (!titleChanged || (!string.IsNullOrEmpty(title) && !IsGuid(title)))
					{
						changedFileNameTo = PathFormat.EnsureFileName(title) + TopicFileExtension;

						target = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(target), changedFileNameTo);
					}
				}

				if (changedFileNameTo == null)
				{
					if (topicFileNames != null && topicFileNames.ContainsKey(idString))
					{
						string title = topicFileNames[idString];

						if (!string.IsNullOrEmpty(title))
						{
							changedFileNameTo = PathFormat.EnsureFileName(title) + TopicFileExtension;

							target = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(target), changedFileNameTo);

							if (sourceMetadata == null)
								sourceMetadata = new TopicMetadata(idString);

							sourceMetadata.Title = title;

							if (string.IsNullOrEmpty(sourceMetadata.TocTitle) || IsGuid(sourceMetadata.TocTitle))
								sourceMetadata.TocTitle = title;
						}
					}
				}
			}

			if (changedFileNameTo != null)
			{
				if (topicFileNames != null)
					topicFileNames[idString] = changedFileNameTo;
			}
			else
				target = System.IO.Path.ChangeExtension(target, TopicFileExtension);

			File.Copy(source, target, true);

			string targetMetadataFile = System.IO.Path.ChangeExtension(target, TopicFileCompanionExtension);

			TopicMetadataXml targetMetadata = new TopicMetadataXml(targetMetadataFile);

			IProjectItem companionItem = project.GetItem(targetMetadataFile);

			if (companionItem.Exists)
				companionItem.Checkout();

			targetMetadata.WriteCompanionFile(id.ToString(), System.IO.Path.GetFileNameWithoutExtension(target), sourceMetadata);

			project.GetItem(target).MakeDependencyFor(targetMetadataFile);
		}

		public void CreateTopicFile(Topic topic, bool createCompanionFile)
		{
			if (topic == null)
				throw new ArgumentNullException("topic");

			CreateTopicFile(topic, null, createCompanionFile);
		}

		public void CreateTopicFile(Topic topic, ConceptualTemplate template, bool createCompanionFile)
		{
			if (topic == null)
				throw new ArgumentNullException("topic");

			CreateTopicFile(topic.Id, topic.File, FileMode.Create, template, createCompanionFile, null);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void CreateTopicFile(string id, string file, FileMode mode, ConceptualTemplate template, bool createCompanionFile, string copyFromCompanionFile)
		{
			string path = System.IO.Path.Combine(topicsPath, file);
			string templatePath = (template == null) ? null : template.FullPath;

			IProjectItem item = project.GetItem(path);

			item.Checkout();

			if (!SystemPath.Equals(templatePath, path) && templatePath != null && File.Exists(templatePath))
			{
				File.Copy(templatePath, path, true);

				try
				{
					XmlDocument document = new XmlDocument();
					document.PreserveWhitespace = true;
					document.Load(path);

					XmlElement root = document.DocumentElement;

					if (root != null)
					{
						XmlAttribute idAttribute = root.Attributes["id"];

						if (idAttribute == null)
						{
							if (root.Name != "topic")
							{
								document.RemoveChild(root);

								XmlNode topicRoot = document.AppendChild(document.CreateElement("topic"));
								topicRoot.Attributes.Append(document.CreateAttribute("id")).Value = id;
								topicRoot.Attributes.Append(document.CreateAttribute("revisionNumber")).Value = "0";
								topicRoot.AppendChild(document.CreateWhitespace(Environment.NewLine));
								topicRoot.AppendChild(root);
								topicRoot.AppendChild(document.CreateWhitespace(Environment.NewLine));
							}
							else
								root.Attributes.Append(document.CreateAttribute("id")).Value = id;
						}
						else
							idAttribute.Value = id;

						document.Save(path);
					}
				}
				// If the topic is imported then it's not required to have a topic element with an id attribute
				catch (XmlException) { }
			}
			else
				WriteDefaultTemplate(id, path, mode);

			item.Show();

			try
			{
				if (createCompanionFile)
				{
					string targetMetadataFile = System.IO.Path.ChangeExtension(path, TopicFileCompanionExtension);

					// NOTE: If the file doesn't exist null is returned but that's OK since null may be passed to the 
					// WriteCompanionFile method that is called below.
					TopicMetadata sourceMetadata = (string.IsNullOrEmpty(copyFromCompanionFile)) ? null : TopicMetadataXml.Load(copyFromCompanionFile, id);
					
					TopicMetadataXml targetMetadata = new TopicMetadataXml(targetMetadataFile);

					IProjectItem companionItem = project.GetItem(targetMetadataFile);
					
					if (companionItem.Exists)
						companionItem.Checkout();

					targetMetadata.WriteCompanionFile(id, System.IO.Path.GetFileNameWithoutExtension(targetMetadataFile), sourceMetadata);

					item.MakeDependencyFor(targetMetadataFile);
				}
			}
			catch (Exception ex)
			{
				Log.Exception(ex, "Error creating XML companion file for topic: " + path);
			}
		}

		public void DeleteTopicsRecursive(IXPathNavigable startElement)
		{
			ForEachElementRecursive(startElement, delegate(IXPathNavigable element)
			{
				string file = Topic.GetFile(element);

				if (!string.IsNullOrEmpty(file))
				{
					string path = System.IO.Path.Combine(topicsPath, file + TopicFileExtension);

					project.GetItem(path).Delete();
				}
			});
		}
		#endregion
	}
}
